﻿using System;
using System.Collections.Generic;
using System.Text;

namespace dllLink
{
    // gestion des modèles du produit
    // diffusion globale du produit
    internal class GlobalModel
    {
        private static double mvYearDays = Model.mvYearDays;
        private static double mvDt = Model.mvDt;
        private static double mvSqrtDt = Model.mvSqrtDt;

        private int mvNbAssets;
        private int mvNbUnderlyings;
        private int mvNbInterestRates;
        private int mvNbExchangeRates;
        private int mvExchangeRatesIndex;
        private int mvInterestRatesIndex;
        private bool[] mvUnderlyingIsUSD;
        private Model[] mvUnderlyingModel;
        private Model[] mvExchangeRateModel;
        private Model[] mvInterestRateModel;
        private double mvDiscount;
        private Boolean mvIsDiscountNormalized;

        private double[,] mvMatCov;
        private double[,] mvMatCorrel;
        private double[,] mvL;
        private double[] mvVol;
        private double[] mvN01;
        private double[] mvWi;

        private int ii;
        private double mvValue;
        private int kk;


        public GlobalModel(int theNbAssets, int theNbUnderlyings, int theNbExchangeRates, int theNbInterestRates, bool[] theUnderlyingIsUSD)
        {
            mvNbAssets = theNbAssets;
            mvNbUnderlyings = theNbUnderlyings;
            mvNbExchangeRates = theNbExchangeRates;
            mvNbInterestRates = theNbInterestRates;
            mvExchangeRatesIndex = theNbUnderlyings;
            mvInterestRatesIndex = mvExchangeRatesIndex + theNbExchangeRates;
            mvUnderlyingIsUSD = theUnderlyingIsUSD;
            mvUnderlyingModel = new ModelMBG[mvNbUnderlyings];
            for (int i = 0; i < mvNbUnderlyings; i++)
            {
                mvUnderlyingModel[i] = new ModelMBG();
            }
            mvExchangeRateModel = new ModelMBG[mvNbExchangeRates];
            for (int i = 0; i < mvNbExchangeRates; i++)
            {
                mvExchangeRateModel[i] = new ModelMBG();
            }
            mvInterestRateModel = new ModelVasicek[mvNbInterestRates];
            for (int i = 0; i < mvNbInterestRates; i++)
            {
                mvInterestRateModel[i] = new ModelVasicek();
            }
        }


        public void mSetParameters(VarCovarEstimator theEstimator, VasicekEstimator[] theRateEstimator)
        {

            //calibration des modèles de taux
            for (int i = 0; i < mvNbInterestRates; i++)
            {
                mvInterestRateModel[i].mSetParameters(theRateEstimator[i]);
            }

            //calcul des variances-covariances
            mvMatCov = theEstimator.mComputeMatCov();
            int n = mvMatCov.GetLength(0);
            mvNbAssets = n;
            mvN01 = new double[n];
            mvWi = new double[n];
            mComputeCorrel();

            //calcul des volatilités
            mvVol = new double[n];
            for (int i = 0; i < n; i++)
            {
                mvVol[i] = Math.Sqrt(mvMatCov[i, i] * mvYearDays);
            }

            //sauvegarde des volatilités des sous-jacents
            double[] mySigma = new double[1];
            for (int i = 0; i < mvNbUnderlyings; i++)
            {
                mySigma[0] = mvVol[i];
                mvUnderlyingModel[i].mSetParameters(mySigma);
            }
            
            //calcul de la décomposition de cholesky de la matrice de corrélation
            gsl_matrix m = gsl.gsl_matrix_alloc(Convert.ToUInt32(n), Convert.ToUInt32(n));
            for (uint i = 0; i < m.size1; i++)
                for (uint j = 0; j < m.size2; j++)
                    gsl.gsl_matrix_set(m, i, j, mvMatCorrel[i, j]);
            int res = GSLFunctions.cholesky(m);
            if (res!=0)
            {
                // en cas de souci lors de la décomposition, levée d'exception
                throw new Exception("Error while computing cholesky decomposition");
            }
            mvL = new double[n, n];
            for (uint i = 0; i < n; i++)
            {
                for (uint j = 0; j <=i; j++)
                    { mvL[i,j] = gsl.gsl_matrix_get(m, i, j); }
                for (uint j = i + 1; j < n; j++)
                    { mvL[i, j] = 0.0; }
            }

        }


        private void mComputeCorrel()
        {
            mvMatCorrel=new double[mvNbAssets,mvNbAssets];
            for (int i = 0; i < mvNbAssets; i++)
            {
                for (int j = 0; j < mvNbAssets; j++)
                {
                    mvMatCorrel[i, j] = mvMatCov[i, j] / Math.Sqrt(mvMatCov[i, i] * mvMatCov[j, j]);
                }
            }
        }


        // génère des v.a. gaussiennes corrélées
        private void mGenerateGaussians()
        {
            // génération des gaussiennes
            for (ii = 0; ii < mvNbAssets; ii++)
            {
                mvN01[ii] = GSLFunctions.getGaussian();
            }
            // multiplication avec la décomposée de cholesky
            for (ii = 0; ii < mvNbAssets; ii++)
            {
                mvValue = 0;
                for (kk = 0; kk <= ii; kk++)
                {
                    mvValue += mvL[ii, kk] * mvN01[kk];
                }
                mvWi[ii] = mvValue;
            }
        }


        public void mDiffuse(ref double[] theCurrentPrices)
        {
            mGenerateGaussians();
            double[] myParams = new double[2];

            // !pour les sous-jacents (OPCVMs) et le change, ce sont les logs qui sont diffusés
            for (int i = 0; i < mvNbUnderlyings; i++)
            {
                //réalisation du brownien associé
                myParams[0] = mvWi[i];
                //choix du taux court (drift)
                if (mvUnderlyingIsUSD[i])
                {
                    myParams[1] = theCurrentPrices[mvInterestRatesIndex+1];
                }
                else
                {
                    myParams[1] = theCurrentPrices[mvInterestRatesIndex];
                }
                //diffusion des prix
                mvUnderlyingModel[i].mDiffuse(ref theCurrentPrices[i], myParams);
            }

            // incrément de l'intégrale du discount
            mvDiscount += theCurrentPrices[mvInterestRatesIndex];

            // diffusion des taux courts
            for (int i = mvInterestRatesIndex; i < mvNbAssets; i++)
            {
                myParams[0] = mvWi[i];
                mvInterestRateModel[i - mvInterestRatesIndex].mDiffuse(ref theCurrentPrices[i], myParams);
            }
        }


        public void mInitDiscount()
        {
            mvDiscount = 0.0;
            mvIsDiscountNormalized = false;
        }


        public double mGetDiscount()
        {
            if (!mvIsDiscountNormalized)
            {
                mNormDiscount();
            }
            return mvDiscount;
        }
        

        private void mNormDiscount()
        {
            mvDiscount = Math.Exp(-mvDiscount * mvDt);
            mvIsDiscountNormalized = true;
        }
    }
}
